# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Pairing style stacks
"""
from ..packlib.info import GAP, ACE, JACK, QUEEN, KING
from ..utillib.misc import kwdefault
from .. import pilelib as stack
from .. import helpstrings as hs
from ..gamelib import state
from .. import comparecards as cmp_cards
from ..pilelib import move as a_move

FOURTEEN, FIFTEEN, TWENTYFIVE = 13, 14, 24


class TopDropMethod(stack.SingleAsDoubleClickMethod):
    """
    Drop top which builds on Foundation.
    """

    def assertStack(self):
        """
        assertStack() -> None

        :Exceptions:
          `AssertionError` when `game.s.foundations` has more than 1 pile.

        :seealso:
          `pysollib.stack.Stack` for further exceptions made by parent.
        """
        super(TopDropMethod, self).assertStack()
        assert len(self.game.s['foundations']) == 1

    def cardsBuild(self, **kw):
        """
        cardsBuild(from_stack, cards) -> bool

        Check whether pile is a gap and parent method.

        :Parameters:
          from_stack : pysollib.stack.Stack
            pile from which cards playing.

          cards : tuple of ..pilelib.card.Card
            current card seq playing to pile.

        :return: pile can build with cards from played from_stack?

        seealso::
          `pysollib.stackcaps.builds` for the *capability* check.
        """
        return not self.cards and super(TopDropMethod, self).cardsBuild(**kw)


class PairDropMethod(object):
    """
    Build none. Play top. Drop pair of from_stack and to_stack which build
    as pair on Foundation. Base none.
    """

    def __init__(self, x, y, game, **cap):
        """
        __init__(x, y, game[, max_accept=1, base_rank=GAP, dir=0]) -> None

        :Parameters:
          x : int
            left x-coordinate.

          y : int
            top y-coordinate.

          game : pysollib.game.Game
            current Game.

        seealso::
          `pysollib.stackcaps` with details of *capability* keywords.
        """
        kwdefault(cap, max_accept=1, base_rank=GAP, dir=0)
        super(PairDropMethod, self).__init__(x, y, game, **cap)

    def assertStack(self):
        """
        assertStack() -> None

        :Exceptions:
          `AssertionError` when `game.s.foundations` has more than 1 pile.

        :seealso:
          `pysollib.stack.Stack` for further exceptions made by parent.
        """
        super(PairDropMethod, self).assertStack()
        assert len(self.game.s['foundations']) == 1

    def move_pile(self, ncards, to_stack, **kw):
        """
        Call next method in order when to stack has base rank not GAP, is empty
        or to_stack in s[foundations]. Else override with pair drop method.

        .. move_pile(ncards:int, to_stack:Pile[, *animation keywords*]) -> None
        """
        if (not any([to_stack.cap['base_rank'] == GAP, to_stack.cards]) or
            to_stack in self.game.s['foundations']):
            super(PairDropMethod, self).move_pile(ncards, to_stack, **kw)
        else:
            assert self is not stack and ncards == 1
            self.game._move_do(a_move.ADropPairMove(self, to_stack, None, **kw),
                               reset=True)  # low level
            self.fill_stack()
            to_stack.fill_stack()

    def _sequence(self, cards):
        """
        _sequence(cards) -> bool

        :Parameters:
          cards : tuple of ..pilelib.card.Card
            current card seq playing to pile.

        :return: whether card sequence matches acccording to *capabilities*.
        """
        return self.game.s['foundations'][0]._sequence(cards)

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack, cards) -> bool

        Check whether pile is a gap, pair top from self and from_stack is
        `_sequence` and parent method.

        :Parameters:
          from_stack : pysollib.stack.Stack
            pile from which cards playing.

          cards : tuple of ..pilelib.card.Card
            current card seq playing to pile.

        :return: pile can build with cards from played from_stack?

        seealso::
          `pysollib.stackcaps.builds` for the *capability* check.
        """
        return (any([not self.cards, self._sequence(self.cards[-1:] + cards)])
                and super(PairDropMethod, self).cardsBuild(from_stack, cards))


class TopPairDropMethod(TopDropMethod, PairDropMethod):

    def _topcard(self):
        """
        _topcard() -> bool

        :return: accept top card acccording to *capabilities*?
        :rtype: boolean
        """
        return self._sequence([self.TOPCARD])


class LR_SS_SingleFoundation(stack.FromRowStackBuildMethod,
                             stack.SingleFoundation):

    def __init__(self, x, y, game, **cap):
        """
        __init__(x, y, game[, max_accept=1]) -> None

        :Parameters:
          x : int
            left x-coordinate.

          y : int
            top y-coordinate.

          game : pysollib.game.Game
            current Game.

        seealso::
          `pysollib.stackcaps` with details of *capability* keywords.
        """
        cap.setdefault('max_accept', 1)
        super(LR_SS_SingleFoundation, self).__init__(x, y, game, **cap)

    def _sequence(self, cards):
        """
        _sequence(cards) -> bool

        :Parameters:
          cards : tuple of ..pilelib.card.Card
            current card seq playing to pile.

        :return: whether card sequence matches acccording to *capabilities*.

        important:: This method should be defined in any subclass.
        """
        card = cards[0]
        if not cmp_cards.cardsFaceUp(card) or card == ACE:
            return False
        for oc in (pile.TOPCARD for pile in self.game.s['rows'] if pile.cards):
            if (cardsFaceUp(oc) and cmp_cards.isSameSuit([card] + [oc]) and
                card['rank'] < oc['rank']):
                return True
        else:
            False

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack, cards) -> bool

        Check whether pile is a gap, pair top from self and from_stack is
        `_sequence` and parent method.

        :Parameters:
          from_stack : pysollib.stack.Stack
            pile from which cards playing.

          cards : tuple of ..pilelib.card.Card
            current card seq playing to pile.

        :return: pile can build with cards from played from_stack?

        seealso::
          `pysollib.stackcaps.builds` for the *capability* check.
        """
        return (not self.cards or super(LR_SS_SingleFoundation, self).cardsBuild(from_stack, cards))

class PairSS_SingleFoundation(stack.FromRowStackBuildMethod,
                             stack.SingleFoundation):

    def __init__(self, x, y, game, **cap):
        """
        __init__(x, y, game[, max_accept=2, min_accept=2]) -> None

        :Parameters:
          x : int
            left x-coordinate.

          y : int
            top y-coordinate.

          game : pysollib.game.Game
            current Game.

        seealso::
          `pysollib.stackcaps` with details of *capability* keywords.
        """
        kwdefault(cap, max_accept=2, min_accept=2)
        super(PairSS_SingleFoundation, self).__init__(x, y, game, **cap)

    def _sequence(self, cards):
        """
        _sequence(cards) -> bool

        :Parameters:
          cards : tuple of ..pilelib.card.Card
            current card seq playing to pile.

        :return: whether card sequence matches acccording to *capabilities*.

        important:: This method should be defined in any subclass.
        """
        return cmp_cards.isSameSuit(cards[0])


class SetRK_SingleFoundation(stack.SingleFoundation):

    def __init__(self, x, y, game, **cap):
        """
        __init__(x, y, game[, max_accept=4, min_accept=4]) -> None

        :Parameters:
          x : int
            left x-coordinate.

          y : int
            top y-coordinate.

          game : pysollib.game.Game
            current Game.

        seealso::
          `pysollib.stackcaps` with details of *capability* keywords.
        """
        kwdefault(cap, min_accept=4, max_accept=4)
        super(SetRK_SingleFoundation, self).__init__(x, y, game, **cap)

    def _sequence(self, cards):
        """
        _sequence(cards) -> bool

        :Parameters:
          cards : tuple of ..pilelib.card.Card
            current card seq playing to pile.

        :return: whether card sequence matches acccording to *capabilities*.

        important:: This method should be defined in any subclass.
        """
        return cmp_cards.isSameRank(cards)


class PairRK_SingleFoundation(stack.SingleFoundation):

    def __init__(self, x, y, game, **cap):
        """
        __init__(x, y, game[, max_accept=2, min_accept=2]) -> None

        :Parameters:
          x : int
            left x-coordinate.

          y : int
            top y-coordinate.

          game : pysollib.game.Game
            current Game.

        seealso::
          `pysollib.stackcaps` with details of *capability* keywords.
        """
        kwdefault(cap, min_accept=2, max_accept=2)
        super(PairRK_SingleFoundation, self).__init__(x, y, game, **cap)


class TopDropRowStack(TopDropMethod, stack.BasicRowStack):
    pass


class PairDropRowStack(PairDropMethod, stack.BasicRowStack):
    pass


class SetRK_RowStack(stack.RK_RowStack):

    def __init__(self, x, y, game, **cap):
        """
        __init__(x, y, game[, max_accept=1, max_move=1, dir=0]) -> None

        :Parameters:
          x : int
            left x-coordinate.

          y : int
            top y-coordinate.

          game : pysollib.game.Game
            current Game.

        seealso::
          `pysollib.stackcaps` with details of *capability* keywords.
        """
        kwdefault(cap, max_move=1, max_accept=1, dir=0)
        super(SetRK_RowStack, self).__init__(x, y, game, **cap)

    def cardsDrop(self, stacks):
        """
        cardsDrop(stacks) -> tuple

        Drop SEQUENCE that builds on `game.s.foundations[0]` or parent method.

        :Parameters:
          stacks : tuple of pysollib.stack.Stacks
            piles to check if SEQUENCE builds.

        :return: tuple of pile to drop SEQUENCE and SEQUENCE len.
        """
        pile, foundations = self.SEQUENCE, self.game.s.foundations
        if stacks is foundations:
            if foundations[0].cardsBuild(self, pile):
                return (foundations[0], len(pile))
            else:
                return (None, 0)
        return super(SetRK_RowStack, self).cardsDrop(stacks)


class PairRankOrSeqMethod(PairDropMethod):
    """
    Build none. Play top. Drop pair of from_stack and to_stack which build
    either in rank alone or same rank on _foundation_. Base none.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Checks cards are either sequence or same rank.

        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return (cmp_cards.isEitherRankSequence(cards, **self.caps) or
                cmp_cards.isSameRank(cards))

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.PAIR_EITHER_SAME


class PairRK_RowStack(PairDropMethod, stack.BasicRowStack):
    """
    Build none. Play top. Drop pair by same rank on Game.s.foundations[0].
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        return cmp_cards.isSameRank(cards)


# Summation Style::
#  Ranks are summed and sometimes checked for other attributes.

class SumFourteenMethod(PairDropMethod):

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Sequence is cards rank sum of 14.

        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return cmp_cards.sumOf(cards, FOURTEEN)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.PAIR_SUM_FOURTEEN


class SumDropKingMethod(TopPairDropMethod):

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Sequence is rank sum of 13.

        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return cmp_cards.sumOf(cards, KING)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return '{0} {1}'.format(hs.PAIR_SUM_THIRTEEN, hs.PAIR_KING_ALONE)


class SumJack_RowStack(PairDropMethod, stack.BasicRowStack):
    """
    Build none. Play top. Drop pair by sum JACK on Game.s.foundations[0].
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Checks sequence sum is the value of 11

        :param cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return cmp_cards.sumOf(cards, JACK)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.PAIR_SUM_ELEVEN


class SumJackSS_Rowstack(SumJack_RowStack):
    """
    Build none. Play top. Drop pair by sum JACK by same suit on
    Game.s.foundations[0].
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return (super(SumJackSS_Rowstack, self)._sequence(cards) and
                cmp_cards.isSameSuit(cards))

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.PAIR_SUM_ELEVEN_SUIT


class SumJackorSeqQueenKing_RowStack(SumJack_RowStack):
    """
    Build none. Play top. Drop pair by sum JACK or seq QUEEN KING on
    Game.s.foundations[0].
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Checks parent _isSeqeunce() or cards are JACKs or sum is QUEEN + KING.

        :param cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return (super(SumJackorSeqQueenKing_RowStack, self)._sequence(cards)
                or cmp_cards.isSameRank(cards, JACK) or
                cmp_cards.isSumOf(cards, QUEEN + KING))

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return '{0} {1} {0}'.format(
                    super(SumJackorSeqQueenKing_RowStack, self)._helpString,
                    hs.PAIR_JACK, hs.PAIR_QUEEN_KING)


class SumFourteen_RowStack(PairDropMethod, stack.BasicRowStack):
    """
    Build none. Play top. Drop pair by sum FOURTEEN on Game.s.foundations[0].
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param cards: cards from sending pile
        :return: accept card sequence acccording to *capabilities*?
        :rtype: boolean
        """
        return cmp_cards.sumOf(cards, FOURTEEN)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.PAIR_SUM_FOURTEEN


# Monte Carlo Style Pairing::
#   Cards must be neighbors as determined by Game.isNeighbor()

class NeighborCardMethod(object):
    """
    Build none. Play top. Drop card pair by parent methods that is
    Game.isNeighbour() on Game.s.foundations[0].
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:tup) -> bool

        Check parent method if can build with cards from from_stack and
        from_stack is Game.isNeighbour() pile.

        :param from_stack: pile playing cards.
        :param cards: cards playing on pile.
        :return: pile can build with cards from played from_stack?
        :rtype: boolean

        seealso::
          Method pysollib.stackcaps.builds for the *capability* check.
        """
        return (super(NeighborCardMethod, self).cardsBuild(from_stack, cards)
                and self.game.isNeighbour(from_stack, self))


class PairNeighborRK_RowStack(NeighborCardMethod, PairRK_RowStack):
    """
    Build none. Play top. Drop neighbor pair by same rank on
    Game.s.foundations[0].
    """
    pass


class SumNeighborFourteen_RowStack(NeighborCardMethod, SumFourteen_RowStack):
    """
    Build none. Play top. Drop neighbor pair by sum FOURTEEN on
    Game.s.foundations[0].
    """
    pass


class SumKing_RowStack(SumDropKingMethod, stack.BasicRowStack):
    """
    Build none. Play top. Drop pair by sum KING or KING on
    Game.s.foundations[0].
    """
    pass


class SumNeighborKing_RowStack(NeighborCardMethod, SumKing_RowStack):
    """
    Build none. Play top. Drop neighbor pair by sum KING or KING on
    Game.s.foundations[0].
    """
    pass